<!DOCTYPE HTML>
<html lang="en">

<head>
    <title>Tree</title>
    <meta charset="utf-8">

    <style type="text/css">
        html,
        body {
            margin: 0px;
            padding: 0px;
            overflow: hidden;
            background-color: #170d9b;
            background: -webkit-linear-gradient(#170d9b, rgba(0, 0, 0, 1));
        }

        canvas {
            background-color: #170d9b;
            background: -webkit-linear-gradient(#170d9b, rgba(0, 0, 0, 1));
        }
    </style>
</head>

<body>
    <canvas id='canvas'></canvas>
    <div id="statMsg" style="display:none;"></div>
    <script type="text/javascript">
        (function () {
            var Vector = function (x, y) {
                this.x = x || 0;
                this.y = y || 0;
            };
            Vector.prototype = {
                add: function (v) {
                    this.x += v.x;
                    this.y += v.y;
                    return this;
                },
                length: function () {
                    return Math.sqrt(this.x * this.x + this.y * this.y);
                },
                rotate: function (theta) {
                    var x = this.x;
                    var y = this.y;
                    this.x = Math.cos(theta) * this.x - Math.sin(theta) * this.y;
                    this.y = Math.sin(theta) * this.x + Math.cos(theta) * this.y;
                    //this.x = Math.cos(theta) * x - Math.sin(theta) * y;
                    //this.y = Math.sin(theta) * x + Math.cos(theta) * y;
                    return this;
                },
                mult: function (f) {
                    this.x *= f;
                    this.y *= f;
                    return this;
                }
            };
            var Leaf = function (p, r, c, ctx) {
                this.p = p || null;
                this.r = r || 0;
                this.c = c || 'rgba(255,255,255,1.0)';
                this.ctx = ctx;
            }
            Leaf.prototype = {
                render: function () {
                    var that = this;
                    var ctx = this.ctx;
                    var f = Branch.random(1, 2)
                    for (var i = 0; i < 5; i++) {
                        (function (r) {
                            setTimeout(function () {
                                ctx.beginPath();
                                ctx.fillStyle = that.color;
                                ctx.moveTo(that.p.x, that.p.y);
                                ctx.arc(that.p.x, that.p.y, r, 0, Branch.circle, true);
                                ctx.fill();
                            }, r * 60);
                        })(i);
                    }
                }
            }
            var Branch = function (p, v, r, c, t) {
                this.p = p || null;
                this.v = v || null;
                this.r = r || 0;
                this.length = 0;
                this.generation = 1;
                this.tree = t || null;
                this.color = c || 'rgba(255,255,255,1.0)';
                this.register();
            };
            Branch.prototype = {
                register: function () {
                    this.tree.addBranch(this);
                },
                draw: function () {
                    var ctx = this.tree.ctx;
                    ctx.beginPath();
                    ctx.fillStyle = this.color;
                    ctx.moveTo(this.p.x, this.p.y);
                    ctx.arc(this.p.x, this.p.y, this.r, 0, Branch.circle, true);
                    ctx.fill();
                },
                modify: function () {
                    var angle = 0.18 - (0.10 / this.generation);
                    this.p.add(this.v);
                    this.length += this.v.length();
                    this.r *= 0.99;
                    this.v.rotate(Branch.random(-angle, angle)); //.mult(0.996);
                    if (this.r < 0.8 || this.generation > 10) {
                        this.tree.removeBranch(this);
                        var l = new Leaf(this.p, 10, this.color, this.tree.ctx);
                        l.render();
                    }
                },
                grow: function () {
                    this.draw();
                    this.modify();
                    this.fork();
                },
                fork: function () {
                    var p = this.length - Branch.random(100, 200); // + (this.generation * 10);
                    if (p > 0) {
                        var n = Math.round(Branch.random(1, 3));
                        this.tree.stat.fork += n - 1;
                        for (var i = 0; i < n; i++) {
                            Branch.clone(this);
                        }
                        this.tree.removeBranch(this);
                    }
                }
            };
            Branch.circle = 2 * Math.PI;
            Branch.random = function (min, max) {
                return Math.random() * (max - min) + min;
            };
            Branch.clone = function (b) {
                var r = new Branch(new Vector(b.p.x, b.p.y), new Vector(b.v.x, b.v.y), b.r, b.color, b.tree);
                r.generation = b.generation + 1;
                return r;
            };
            Branch.rgba = function (r, g, b, a) {
                return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
            };
            Branch.randomrgba = function (min, max, a) {
                return Branch.rgba(Math.round(Branch.random(min, max)), Math.round(Branch.random(min, max)), Math.round(Branch.random(min, max)), a);
            };
            var Tree = function () {
                var branches = [];
                var timer;
                this.stat = {
                    fork: 0,
                    length: 0
                };
                this.addBranch = function (b) {
                    branches.push(b);
                };
                this.removeBranch = function (b) {
                    for (var i = 0; i < branches.length; i++) {
                        if (branches[i] === b) {
                            branches.splice(i, 1);
                            return;
                        }
                    }
                };
                this.render = function (fn) {
                    var that = this;
                    timer = setInterval(function () {
                        fn.apply(that, arguments);
                        if (branches.length > 0) {
                            for (var i = 0; i < branches.length; i++) {
                                branches[i].grow();
                            }
                        }
                        else {
                            //clearInterval(timer);
                        }
                    }, 100 / 30);
                };
                this.init = function (ctx) {
                    this.ctx = ctx;
                };
                this.abort = function () {
                    branches = [];
                    this.stat = {
                        fork: 0,
                        length: 0
                    }
                };
            };
            (function init() {
                // init               
                var canvas_width = window.innerWidth;
                var canvas_height = window.innerHeight;
                var center_x = canvas_width / 5;
                var stretch_factor = 600 / canvas_height;
                var y_speed = 3 / stretch_factor;
                //var $statMsg = $("#statMsg");
                // tx
                var canvas = document.querySelector('canvas');
                canvas.width = canvas_width;
                canvas.height = canvas_height;
                var ctx = canvas.getContext("2d");
                ctx.globalCompositeOperation = "lighter";
                // tree
                var t = new Tree();
                t.init(ctx);
                for (var i = 0; i < 4; i++) {
                    new Branch(new Vector(center_x, canvas_height), new Vector(Math.random(-1, 1), -y_speed), 15 / stretch_factor, Branch.randomrgba(0, 255, 0.3), t);
                }
                t.render(function () {
                    //$statMsg.html(this.stat.fork);
                });
            })();

        })();
    </script>



</body>

</html>